
/* -----------------------------------------------------------------------------
FILE: shell.c
 
NAME:S. Reese Troup

DESCRIPTION: A SHELL SKELETON
-------------------------------------------------------------------------------*/

#include "parse.h"   // local file include declarations for parse-related structs
#include "shell.h"
#include "joblist.h"

/* -----------------------------------------------------------------------------
FUNCTION: buildPrompt()
DESCRIPTION:
-------------------------------------------------------------------------------*/
char * buildPrompt()
{
    char* prompt = malloc(256);
    struct passwd *passwd;
    
    
    /* Get the user id */
    passwd = getpwuid (getuid());
    prompt = strcat(passwd->pw_name, ":");
    prompt = strcat(prompt, "{");
    /* Get the current working directory */
    prompt = strcat(prompt, getcwd(NULL, 256));
    prompt = strcat(prompt, "}");
    prompt = strcat(prompt, "$");
    return prompt;
}
 
/* -----------------------------------------------------------------------------
FUNCTION: isBuild()
DESCRIPTION:
-------------------------------------------------------------------------------*/
int isBuiltInCommand( char * cmd )
{
  if( strncmp(cmd, "exit", strlen( "exit" ) ) == 0 )
  {
	return EXIT;
  }
  else if (strncmp(cmd, "history", strlen("history")) == 0)
  {
     return HISTORY;
  }
  else if (strncmp(cmd, "cd", strlen("cd")) == 0)
  {
     return CD;
  }
  else if (strncmp(cmd, "kill", strlen("kill")) == 0)
  {
     return KILL;
  }
  else if (strncmp(cmd, "jobs", strlen("jobs")) == 0)
  {
     return JOBS;
  }
  else if (strncmp(cmd, "help", strlen("help")) == 0)
  {
     return HELP;
  }
  return NO_SUCH_BUILTIN;
}


/* -----------------------------------------------------------------------------
FUNCTION: main()
DESCRIPTION:
-------------------------------------------------------------------------------*/
int main( int argc, char **argv )
{
  char * cmdLine;
  char *backproc;
  parseInfo *info; 		// info stores all the information returned by parser.
  struct commandType *com; 	// com stores command name and Arg list for one command.
  pid_t childpid;
  pid_t wait_pid;  
  char *expansion;
  int result;
  int builtIn;

  fprintf( stdout, "This is the SHELL version 0.1\n" ) ;
  
  /* Set up variables for signal handling */
  struct sigaction jumphd;
  struct sigaction defaulthd;
  sigset_t blockmask;
  /* Set up a mask to block SIGINT and SIGQUIT */
  sigemptyset(&blockmask);
  sigaddset(&blockmask, SIGINT);
  sigaddset(&blockmask, SIGQUIT);
  /* Set up the handlers for prompt and default */
  jumphd.sa_handler = jumphand;
  jumphd.sa_mask = blockmask;
  jumphd.sa_flags = 0;
  defaulthd.sa_handler = SIG_DFL;
  sigemptyset(&defaulthd.sa_mask);
  defaulthd.sa_flags = 0;
  /* Set up to handle jump to prompt */
  if((sigaction(SIGINT, &jumphd, NULL) < 0) ||
     (sigaction(SIGQUIT, &jumphd, NULL) < 0))
  {
     perror("Shell failed to install signal handlers");
     exit(1);
  }

  while(1)
  {
     if(sigsetjmp(jump_to_prompt, 1))
     {
        /* Redisplay prompt on a new line if return from signal */
        fputs("\n", stdout);
     }
     okaytojump = 1;
     
     using_history();
     stifle_history(MAX_HISTORY_NUM);
    	
    	cmdLine = readline( buildPrompt() ) ;
    	if( cmdLine == NULL ) 
	{
      		fprintf(stderr, "Unable to read command\n");
      		continue;
    	}
     else{
        // insert your code about history and !x !-x here
        result = history_expand (cmdLine, &expansion);
        if (result)
           fprintf (stderr, "%s\n", expansion);

        if (result < 0 || result == 2)
        {
           free (expansion);
           continue;
        }
        add_history(expansion);
        strncpy (cmdLine, expansion, sizeof (cmdLine) - 1);
        free (expansion);
     }

    	// calls the parser
    	info = parse( cmdLine );
        if( info == NULL )
	{
      		free(cmdLine);
      		continue;
    	}

    	// prints the info struct
    	print_info( info );

    	//com contains the info. of the command before the first "|"
    	com = &info->CommArray[0];
    	if( (com == NULL)  || (com->command == NULL)) 
    	{
      		free_info(info);
      		free(cmdLine);
      		continue;
    	}

    	//com->command tells the command name of com
    	if((builtIn = isBuiltInCommand( com->command )) != NO_SUCH_BUILTIN )
	{
	   if(builtIn == HISTORY)
	   {
	      handle_history(com);
	   }
	   else if(builtIn == EXIT)
	   {
	      write_history("history_file");
	      exit(1);
	   }
	   else if(builtIn == CD)
	   {
	      changeDirectory(com);
	   }
	   else if(builtIn == KILL)
	   {
	      if(strstr(com->VarList[0], "%") != NULL)
	      {   //find process in job list
	          //get pid of job
	          //call kill to kill the job
	      }
	      else killJob(atoi(com->VarList[0]));
	   }
    	}

     //if not a built in command
     else
     {
        sigprocmask(SIG_BLOCK, &blockmask, NULL);
        if((childpid = fork()) == 0)
        {
           if((sigaction(SIGINT, &defaulthd, NULL) < 0) ||
              (sigaction(SIGQUIT, &defaulthd, NULL) < 0))
           {
              perror("Child could not restore default handlers");
              exit(1);
           }
           sigprocmask(SIG_UNBLOCK, &blockmask, NULL);
           /* Handle background processes */
           if(info->boolBackground)
              if(setpgid(getpid(), getpid()) == -1) exit(1);
           executeCommand(info);
           exit(1);    
        }
        else if(childpid > 0){
           if(!info->boolBackground)
              while((wait_pid = waitpid(-1, NULL, 0)) > 0)
                 if(wait_pid == childpid) break;
           while(waitpid(-1, NULL, WNOHANG) > 0) ;
        }
        sigprocmask(SIG_UNBLOCK, &blockmask, NULL);
     }
     free_info(info);
     free(cmdLine);

  }/* while(1) */
   
  exit(0); 
}/* end main */


/* Execute user command */
void executeCommand(parseInfo *info)
{
    int i, j, jobNumber;
    struct commandType *comm;
    char **chargv;
    pid_t childpid = 0;
    char *cmd;
    char *nextcmd;
    int frontfd[2];
    int backfd[2];

    frontfd[0] = -1;
    frontfd[1] = -1;
    backfd[0] = -1;
    backfd[1] = -1;

    /* If there are no commands, then exit */
    if ((NULL == info->CommArray) || (NULL == info->CommArray->command)) {
        exit(1);
    }

    for(i = 0; i <= info->pipeNum; i++){
        /* get the next command from the command array */
        comm = &(info->CommArray[i]);
        fputs(comm->command, stdout);
        fputs("\n", stdout);
        /* if last in pipeline, do not fork another */
        if(i == info->pipeNum){
            backfd[1] = -1;
            childpid = 0;
        }
        /* fork a child to execute next pipeline command */
        else{
            if(pipe(backfd) == -1){
                perror("Could not create back pipe");
                exit(1);
            }else if((childpid = fork()) == -1){
                perror("Could not fork next child");
                exit(1);
            }
        }
        /* the child execs the command */
        if(childpid == 0){
            if(pipeline(info, frontfd, backfd) == -1){
                perror("Could not connect to pipeline");
                exit(1);
            }else if(info->CommArray->command != NULL){
                if(execvp(info->CommArray->command, info->CommArray->VarList) == -1)
                    perror("Invalid command");
            }
            exit(1);
        }
        /* The parent closes front pipe and makes back pipe, front */
        close(frontfd[0]);
        close(frontfd[1]);
        frontfd[0] = backfd[0];
        frontfd[1] = backfd[1];
    }
    close(backfd[0]);
    close(backfd[1]);
    exit(1);
}/* end executecommand */
  

/* Redirects standard output to outfilename and standard input to
 * infilename. If either infilename or outfilename is NULL
 * redirection does not occur.
 * Returns 0 if successsful, else -1
 */
int redirect(char *infilename, char *outfilename)
{
   int indes;
   int outdes;
   
   /* Redirect stdin to infilename */
   if(strlen(infilename) > 0)
   {
      if((indes = open(infilename, O_RDONLY, STDMODE)) == -1) return -1;
      if(dup2(indes, STDIN_FILENO) == -1)
      {
         close(indes);
         return -1;
      }
      close(indes);
   }
   
   /* Redirect stdout to outfilename */
   if(strlen(outfilename) > 0) 
   {
      if((outdes = open(outfilename, O_WRONLY|O_CREAT|O_EXCL, STDMODE)) == -1) 
         return -1;
      if(dup2(outdes, STDOUT_FILENO) == -1)
      {
         close(outdes);
         return -1;
      }
      close(outdes);
   }
   return 0;
} /* end redirect */


/**
 * Redirects standard input to front[0] and standard output to back[1]
 * Returns 0 if successful, -1 on error.
 */
int pipeline(parseInfo *info, int frontfd[], int backfd[])
{
    int error = 0;

    if((info->boolInfile == 1) && (strlen(info->inFile) ==0 )){
        fprintf(stdout, "Missing name for redirect.\n");
        error = -1;
    }
    /* No redirection allowed at front of pipeline */
    else if((strlen(info->inFile) > 0) &&  frontfd[0] != -1){
        fprintf(stdout, "Pipeline connection error.\n");
        error = -1;
    }
    else if((info->boolOutfile == 1) && (strlen(info->outFile) == 0)){
        fprintf(stdout, "Missing name for redirect.\n");
        error = -1;
    }
    /* No redirection allowed at the back of pipeline */
    else if((strlen(info->outFile) > 0) && backfd[1] != -1){
        fprintf(stdout, "Pipeline connection error.\n");
        error = -1;
    }
    else if(redirect(info->inFile, info->outFile) == -1){
        fprintf(stdout, "No such file or directory.\n");
        error = -1;
    }
    else{
        /* connect the appropriate pipes */
        if(frontfd[0] != -1){
            if(dup2(frontfd[0], STDIN_FILENO) == -1){
                perror("Error with pipe on front file descriptor\n");
                error = -1;
            }
        }
        if(backfd[1] != -1){
            if(dup2(backfd[1], STDOUT_FILENO) == -1){
                perror("Error with pipe on back file descriptor\n");
                error = -1;
            }
        }
    }
    close(frontfd[0]);
    close(frontfd[1]);
    close(backfd[0]);
    close(backfd[1]);
    return error;
}/* end pipeline */


static void jumphand(int signalnum)
{
   if(!okaytojump) return;
   okaytojump = 0;
   siglongjmp(jump_to_prompt, 1);
} /* end jumphand */


/**
 * Parses the history commands to extract arguments and then executes.
 */
void handle_history(struct commandType *comm)
{
    int i;
    /* parse the history command to handle the arguments */
    if(comm->VarNum > 1){
        for(i = 0; i < comm->VarNum; i++){
            /* if history -c then clear the history list */
            if(strstr(comm->VarList[i], "-c") != NULL){
                clear_history();
            }
        }
    }
    /* If "history" then print history list up to max entries */
    else{
        register HIST_ENTRY **the_list;
        register int i;

        the_list = history_list ();
        if (the_list)
            for (i = 0; the_list[i]; i++)
                printf ("%d: %s\n", i + history_base, the_list[i]->line);
        fflush(stdout);
    }
}/* end handle_history */


/**
 * changes current directory
 */
void changeDirectory(struct commandType *com)
{
   if (com->VarList[1] == NULL) 
   {
      chdir(getenv("HOME"));
   } 
   else 
   {
      if (chdir(com->VarList[1]) == -1) 
      {  
         printf(" %s: no such directory\n", com->VarList[1]);
      }
   }
}/* end changeDirectory */


/**
 * Kill job by pid
 */
void killJob(pid_t killPid)
{
   if(kill(killPid, SIGKILL) == -1)
      perror("Could not send signal");
} /* end killJob(pid_t) */
